// todo: implement rs_istream::get_eol () and use it for rs_ostream
// todo: for disk-intensive operations, make sure that disk is reading all the time and not waiting for
//       processor to request the next data; data should be always either in buffer or being read

#define _CRT_SECURE_NO_DEPRECATE

#include <iostream>
#include "rs_stream.h"
#include "rs_string.h"
#include "rs_log.h"


const char *signature_utf16= "\xff\xfe"; // utf-16le
const char *signature_utf8 = "\xef\xbb\xbf";
const int max_signature_length=3;


void rs_istream::open (const rs_string& fname, rs_encoding encoding, bool report_failure)
{
    m_file_enc = encoding;
    f = fopen (stringed(fname).c_str(), "rb");
    if (!f) {
        if (report_failure)
            rs_report_error (L"problems opening input file: ", fname);
        return;
    }
    seek (0, SEEK_END);
    m_file_size = get_pos();
    seek (0, SEEK_SET);
    if (encoding == rse_autodetect) {
        if (check_unicode_signature (signature_utf8, rse_utf8))
            return;
        if (check_unicode_signature (signature_utf16, rse_utf16))
            return;
        m_file_enc = rse_ascii;
    }
}


bool rs_istream::check_unicode_signature (const char *signature, rs_encoding encoding)
{ // if file has specified unicode signature, sets m_file_enc to 'encoding'
    // seeks to the end of signature and returns true
    // otherwise rewinds the stream and returns false
    char file_sig[max_signature_length];
    size_t tried_sig_len=strlen(signature);
    if (tried_sig_len>max_signature_length) {
        rs_report_error (L"tried signature is too long");
        return false;
    }
    if (m_file_size<tried_sig_len)
        return false;
    seek (0, SEEK_SET);
    if (!read (file_sig, tried_sig_len)) {
        seek (0, SEEK_SET);
        rs_report_error (L"trying to read unicode signature");
        return false;
    }
    if (memcmp (signature, file_sig, tried_sig_len) != 0) {
        seek (0, SEEK_SET);
        return false; // no such signature
    }
    m_file_enc = encoding;
    return true;
}


bool rs_istream::seek (int64 pos, int origin)
{
    if (!f)
        return false;
    if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) {
        rs_report_error (L"unknown origin when seeking file");
    }
    int result = _fseeki64(f, pos, origin);
    return true;
}


int64 rs_istream::get_pos (void)
{
    if (!f)
        return -1;
    return _ftelli64(f);
}

bool rs_istream::read (char *buffer, size_t read_amount)
{
    if (!f)
        return false;
    size_t bytes_read=fread(buffer, sizeof(char), read_amount, f);
    return bytes_read == read_amount;
}


bool rs_istream::read_line (wchar_t *line, size_t buf_size_chars, bool cr_ordinary)
{ // if encoding is suitable, reads a line of wide characters, terminates it with L'\0'
    if (m_file_enc != rse_utf16)
        return false;
    *line = L'\0';
    if (!f)
        return false;
    wchar_t wc;
    bool result=true;
    bool anything_read=false;
    while (buf_size_chars-- > 1) {
        if (!read ((char*) &wc, sizeof(wc))) {
            result=anything_read;
            break;
        }
        anything_read=true;
        if (wc!=L'\n'  && wc!=L'\r') {
            *line++ = wc;
            continue;
        }
        if (wc==L'\r'  && cr_ordinary) {
            *line++ = wc;
            continue;
        }
        if (wc == L'\n')
            break;
    }
    *line = L'\0';
    return result;
}


bool rs_istream::read_line (char *line, size_t buf_size_chars, bool cr_ordinary)
{ // if encoding is suitable, reads a line of characters, terminates it with '\0'
    if (m_file_enc == rse_utf16)
        return false;
    *line = '\0';
    if (!f)
        return false;
    char ch;
    bool result=true;
    bool anything_read=false;
    while (buf_size_chars-- > 1) {
        if (!read ((char*) &ch, sizeof(ch))) {
            result=anything_read;
            break;
        }
        anything_read=true;
        if (ch!='\n'  && ch!='\r') {
            *line++ = ch;
            continue;
        }
        if (ch=='\r'  &&  cr_ordinary) {
            *line++ = ch;
            continue;
        }
        if (ch == '\n')
            break;
    }
    *line = '\0';
    return result;
}


bool rs_istream::read_line (wstring& line, bool cr_ordinary)
{ // reads a line of text
    // returns false on error
    const int max_line=10000;
    if (m_file_enc == rse_utf16) {
        wchar_t line_buf[max_line+1];
        if (!read_line (line_buf, max_line, cr_ordinary)) {
            line=L"";
            return false;
        }
        line = line_buf;
    } else {
        char line_buf[max_line+1];
        if (!read_line (line_buf, max_line, cr_ordinary)) {
            line=L"";
            return false;
        }
        utf8_to_wstring (string(line_buf), line);
    }
    return true;
}


bool rs_istream::read_file (wstring& text)
{
    text = L"";
    wstring line;
    while (!eof()) {
        read_line (line);
        text += line;
    }
    return true;
}


bool rs_istream::read_file_bytes (string& bytes)
{
    int64 fsize = file_size();
    const int max_size=1000000;
    static char buf[max_size];
    bytes = "";
    if (fsize>=max_size) {
        rs_report_error (L"implementation limitation: file size limited to " +
            rs_stringed (max_size) + L" bytes");
        return false;
    }
    seek (0, SEEK_SET);
    read (buf, (int) fsize);
    bytes = string (buf, (int) fsize);
    return true;
}



void rs_ostream::init (const rs_string& fname, rs_encoding encoding)
{
    f = fopen (stringed(fname).c_str(), "wb");
    if (!f) {
        rs_report_error (L"problems opening output file: ", fname);
    }
    file_enc = encoding;
    const char *signature="";
    if (encoding == rse_utf16)
        signature = "\xff\xfe"; // utf-16le unicode signature
    else if (encoding==rse_utf8)
        signature = "\xef\xbb\xbf"; // utf-8 unicode signature
    write (signature, -1);
}


bool rs_ostream::write (const void* data, int length)
{ // writes string 'str' to file
    // if length==-1: use strlen to find out actual length
    // if length==-2: use wcslen to find out actual length
    // otherwise length is specified in bytes
    if (length==-1)
        length=(int)strlen((char*)data)*sizeof(char);
    else if (length==-2)
        length=(int)wcslen((wchar_t*)data)*sizeof(wchar_t);
    return length == fwrite (data, sizeof(char), length, f);
}


void rs_ostream::write_line (const wstring& line, bool append_endl)
{ // writes a line to file
    // if append_endl, end-of-line character is not present in argument 'line'
    // and is appended to the output
    if (!f)
        return;
    if (file_enc == rse_utf8) {
        string line_utf8;
        wstring_to_utf8 (line, line_utf8);
        write (line_utf8.c_str());
        if (append_endl)
            write ("\r\n");
    } else if (file_enc == rse_utf16) {
        write (line.c_str(), -2);
        if (append_endl)
            write (L"\r\n", -2);
    } else if (file_enc == rse_ascii) {
        string line_ascii;
        wstring_to_ascii (line, line_ascii);
        write (line_ascii.c_str());
        if (append_endl)
            write ("\n", 1);  // .strings files for AbiWord use LF unly;
            // if \r is to be written, have it in the 'line'
    } else 
        rs_report_error (L"unknown encoding");
}


void rs_ostream::flush()
{
    if (!f)
        return;
    _fseeki64(f, 0, SEEK_CUR);
}


/*
#include <stdio.h>

extern "C" int __cdecl _fseeki64(FILE *, __int64, int);
extern "C" __int64 __cdecl _ftelli64(FILE *);

int setvbuf(
FILE *stream,
char *buffer,
int mode,
size_t size 
);
The mode _IOFBF
Full buffering; that is, buffer is used as the buffer and size is used 
as the size of the buffer. If buffer is NULL, an automatically allocated buffer size bytes long is used.



int _fseeki64( 
FILE *stream,
__int64 offset,
int origin 
);

The argument origin must be one of the following constants, defined in STDIO.H: 

SEEK_CUR
Current position of file pointer.

SEEK_END
End of file.

SEEK_SET


size_t fread( 
void *buffer,
size_t size,
size_t count,
FILE *stream 
);


*/


bool compare_text_files (const rs_string& fname1, const rs_string& fname2)
{
    // checks two files for equality
    // returns true if files are equal
    // also outputs the result
    rs_istream ifs1(fname1);
    rs_istream ifs2(fname2);
    wstring line1, line2;
    unsigned line_no=0;
    rs_log_console (L"Comparing " + fname1 + L" and " + fname2);
    if (!ifs1.good() || !ifs2.good()) {
        rs_log_console (L"!!!!!!! test failed, a file is missing !!!!!!!");
        return false;
    }
    while (!ifs1.eof() && !ifs2.eof()) {
        line_no++;
        ifs1.read_line (line1);
        ifs2.read_line (line2);
        if (line1 != line2) {
            rs_log_console (L"!!!!!!! test failed, line " + rs_stringed(line_no) 
                + L" differs !!!!!!!");
            return false;
        }
    }
    if (!ifs1.eof() || !ifs2.eof()) {
        rs_log_console (L"!!!!!!! test failed, different sizes !!!!!!!");
        return false;
    }
    rs_log_console (L"******* test passed, files are equal *******");
    return true;
}
